Skip to content

fix: retain unmodeled CLI wire fields on Assistant/Result messages#1075

Open
Magic-Man-us wants to merge 1 commit into
anthropics:mainfrom
Magic-Man-us:fix/preserve-raw-frame
Open

fix: retain unmodeled CLI wire fields on Assistant/Result messages#1075
Magic-Man-us wants to merge 1 commit into
anthropics:mainfrom
Magic-Man-us:fix/preserve-raw-frame

Conversation

@Magic-Man-us

Copy link
Copy Markdown

Summary

Fixes #1026. The CLI emits several stream-json fields that parse_message neither reads nor retains, so they vanish from the typed messages. Unlike SystemMessage (which keeps the raw frame as .data), AssistantMessage and ResultMessage kept no reference to the original frame — so consumers could not reach these fields at all without bypassing the SDK and re-parsing the wire.

Dropped fields observed on the wire (CLI 2.1.150):

Wire field Where Example
ttft_ms result (top-level) 2806 (time-to-first-token)
terminal_reason result (top-level) "completed"
fast_mode_state result (top-level) "off"
request_id assistant (top-level) "req_…"
stop_details, diagnostics, context_management assistant message.* observed null
content[].caller assistant content block {"type": "direct"}

Fix — hybrid

The two suggested fixes in the issue (model the fields, or retain the raw frame) each have a gap: pure modeling drifts on the next new CLI field and forces guessed types onto the three fields only ever observed as null; a pure escape hatch gives no typed access to the stable metrics people actually chart. This PR does both:

  • Model the stable scalars as typed attributes — ResultMessage.ttft_ms / terminal_reason / fast_mode_state, and AssistantMessage.request_id — for ergonomic, type-checked access.
  • Add a raw-frame escape hatch data: dict[str, Any] | None to both message types, mirroring the existing SystemMessage.data, so the null-only fields (stop_details, diagnostics, context_management), content[].caller, and any future CLI field stay reachable with no further code changes.

All new fields default to None and are appended last, so no existing construction or positional usage changes.

Test plan

  • ruff check src/ tests/ — clean
  • ruff format --check src/ tests/ — clean
  • mypy src/ — clean
  • pytest tests/ — 987 passed, 5 skipped
  • New parser tests assert both the typed fields and the .data hatch (for an unmodeled/future field); verified failing against main and passing with the change.

parse_message dropped stream-json fields it did not model — ttft_ms,
terminal_reason, fast_mode_state, request_id, stop_details, and others —
because AssistantMessage and ResultMessage, unlike SystemMessage, kept no
reference to the raw frame. SDK consumers could not reach those fields
without bypassing the SDK and re-parsing the wire.

Hybrid fix:
- Model the stable scalar metrics as typed attributes: ResultMessage gains
  ttft_ms / terminal_reason / fast_mode_state; AssistantMessage gains
  request_id.
- Add a raw-frame escape hatch (`data`) to both message types, mirroring
  the existing SystemMessage.data, so unmodeled and future CLI fields stay
  reachable without further code changes.

Fixes anthropics#1026
Copilot AI review requested due to automatic review settings June 27, 2026 21:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes loss of newer/unmodeled Claude Code CLI stream-json fields by (1) adding typed accessors for stable scalar metrics and (2) retaining the raw wire frame on AssistantMessage and ResultMessage as a forward-compatible escape hatch (aligning with the existing SystemMessage.data pattern).

Changes:

  • Add AssistantMessage.request_id and AssistantMessage.data to preserve the top-level request ID and the full raw frame.
  • Add ResultMessage.ttft_ms, terminal_reason, fast_mode_state, and ResultMessage.data to expose stable metrics and preserve the raw frame.
  • Extend parser tests to assert both typed fields and retention of unmodeled/future fields via .data.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
tests/test_message_parser.py Adds coverage ensuring the parser populates new typed fields and retains the raw frame for forward compatibility.
src/claude_agent_sdk/types.py Extends AssistantMessage/ResultMessage dataclasses with typed scalar fields plus optional .data raw-frame storage.
src/claude_agent_sdk/_internal/message_parser.py Populates the new dataclass fields from incoming CLI frames and retains the raw frame on the parsed messages.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

parse_message silently drops newer CLI wire fields (ttft_ms, terminal_reason, stop_details, …) with no escape hatch

2 participants